import { useGo } from '@/hooks/web/usePage'
import { getMenus } from '@/router/menus'
import { type Menu } from '@/router/types'
import { filter, forEach } from '@/utils/helper/treeHelper'
import { useScrollTo } from '@vben/hooks'
import { type AnyFunction } from '@vben/types'
import { onKeyStroke, useDebounceFn } from '@vueuse/core'
import { cloneDeep } from 'lodash-es'
import { Ref, nextTick, onBeforeMount, ref, unref } from 'vue'

export interface SearchResult {
  name: string
  path: string
  icon?: string
  // 搜索结果包含的字符着色
  chars: { char: string; highlight: boolean }[]
}

// Translate special characters
function transform(c: string) {
  const code: string[] = ['$', '(', ')', '*', '+', '.', '[', ']', '?', '\\', '^', '{', '}', '|']
  return code.includes(c) ? `\\${c}` : c
}

function createSearchReg(key: string) {
  const keys = [...key].map((item) => transform(item))
  const str = ['', ...keys, ''].join('.*')
  return new RegExp(str)
}

export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref, emit: AnyFunction) {
  const searchResult = ref<SearchResult[]>([])
  const keyword = ref('')
  const activeIndex = ref(-1)

  let menuList: Menu[] = []

  const go = useGo()
  const handleSearch = useDebounceFn(search, 200)

  onBeforeMount(async () => {
    const list = await getMenus()
    menuList = cloneDeep(list)
    forEach(menuList, (item) => {
      item.name = item.meta?.title || item.name
    })
  })

  function search(e: ChangeEvent) {
    e?.stopPropagation()
    const key = e.target.value
    keyword.value = key.trim().toLowerCase()
    if (!key) {
      searchResult.value = []
      return
    }
    const reg = createSearchReg(unref(keyword))
    const filterMenu = filter(menuList, (item) => {
      return reg.test(item.name?.toLowerCase()) && !item.hideMenu
    })
    searchResult.value = handlerSearchResult(filterMenu, reg)
    activeIndex.value = 0
  }

  function handlerSearchResult(filterMenu: Menu[], reg: RegExp, parent?: Menu) {
    const ret: SearchResult[] = []
    filterMenu.forEach((item) => {
      const { name, path, icon, children, hideMenu, meta } = item
      if (
        !hideMenu &&
        reg.test(name?.toLowerCase() ?? '') &&
        (!children?.length || meta?.hideChildrenMenu)
      ) {
        const chars: { char: string; highlight: boolean }[] = []

        // 显示字符串
        const label = (parent?.name ? `${parent.name} > ${name}` : name) ?? ''
        const labelChars = label.split('')
        let labelPointer = 0

        const keywordChars = keyword.value.split('')
        const keywordLength = keywordChars.length
        let keywordPointer = 0

        // 用于查找完整关键词的匹配
        let includePointer = 0

        // 优先查找完整关键词的匹配
        if (label.toLowerCase().includes(keyword.value.toLowerCase())) {
          while (includePointer < labelChars.length) {
            if (
              label.toLowerCase().slice(includePointer, includePointer + keywordLength) ===
              keyword.value.toLowerCase()
            ) {
              chars.push(
                ...label
                  .substring(labelPointer, includePointer)
                  .split('')
                  .map((v) => ({
                    char: v,
                    highlight: false,
                  })),
              )
              chars.push(
                ...label
                  .slice(includePointer, includePointer + keywordLength)
                  .split('')
                  .map((v) => ({
                    char: v,
                    highlight: true,
                  })),
              )
              includePointer += keywordLength
              labelPointer = includePointer
            } else {
              includePointer++
            }
          }
        }

        // 查找满足关键词顺序的匹配
        while (labelPointer < labelChars.length) {
          keywordPointer = 0
          while (keywordPointer < keywordChars.length) {
            if (keywordChars[keywordPointer] !== void 0 && labelChars[labelPointer] !== void 0) {
              if (
                keywordChars[keywordPointer].toLowerCase() ===
                labelChars[labelPointer].toLowerCase()
              ) {
                chars.push({
                  char: labelChars[labelPointer],
                  highlight: true,
                })
                keywordPointer++
              } else {
                chars.push({
                  char: labelChars[labelPointer],
                  highlight: false,
                })
              }
            } else {
              keywordPointer++
            }
            labelPointer++
          }
        }
        ret.push({
          name: label,
          chars,
          path,
          icon,
        })
      }
      if (!meta?.hideChildrenMenu && Array.isArray(children) && children.length) {
        ret.push(...handlerSearchResult(children, reg, item))
      }
    })

    // 排序
    return ret.sort((a, b) => {
      if (
        a.name.toLowerCase().includes(keyword.value.toLowerCase()) &&
        b.name.toLowerCase().includes(keyword.value.toLowerCase())
      ) {
        // 两者都存在完整关键词的匹配

        // 匹配数量
        const ca =
          a.name.toLowerCase().match(new RegExp(keyword.value.toLowerCase(), 'g'))?.length ?? 0
        const cb =
          b.name.toLowerCase().match(new RegExp(keyword.value.toLowerCase(), 'g'))?.length ?? 0

        // 匹配数量越多的优先显示，数量相同的按字符串排序
        return ca === cb ? a.name.toLowerCase().localeCompare(b.name.toLowerCase()) : cb - ca
      } else {
        if (a.name.toLowerCase().includes(keyword.value.toLowerCase())) {
          // 完整关键词的匹配优先
          return -1
        } else if (b.name.toLowerCase().includes(keyword.value.toLowerCase())) {
          // 完整关键词的匹配优先
          return 1
        } else {
          // 按字符串排序
          return a.name.toLowerCase().localeCompare(b.name.toLowerCase())
        }
      }
    })
  }

  // Activate when the mouse moves to a certain line
  function handleMouseenter(e: any) {
    const index = e.target.dataset.index
    activeIndex.value = Number(index)
  }

  // Arrow key up
  function handleUp() {
    if (!searchResult.value.length) return
    activeIndex.value--
    if (activeIndex.value < 0) {
      activeIndex.value = searchResult.value.length - 1
    }
    handleScroll()
  }

  // Arrow key down
  function handleDown() {
    if (!searchResult.value.length) return
    activeIndex.value++
    if (activeIndex.value > searchResult.value.length - 1) {
      activeIndex.value = 0
    }
    handleScroll()
  }

  // When the keyboard up and down keys move to an invisible place
  // the scroll bar needs to scroll automatically
  function handleScroll() {
    const refList = unref(refs)
    if (!refList || !Array.isArray(refList) || refList.length === 0 || !unref(scrollWrap)) {
      return
    }

    const index = unref(activeIndex)
    const currentRef = refList[index]
    if (!currentRef) {
      return
    }
    const wrapEl = unref(scrollWrap)
    if (!wrapEl) {
      return
    }
    const scrollHeight = currentRef.offsetTop + currentRef.offsetHeight
    const wrapHeight = wrapEl.offsetHeight
    const { start } = useScrollTo({
      el: wrapEl,
      duration: 100,
      to: scrollHeight - wrapHeight,
    })
    start()
  }

  // enter keyboard event
  async function handleEnter() {
    if (!searchResult.value.length) {
      return
    }
    const result = unref(searchResult)
    const index = unref(activeIndex)
    if (result.length === 0 || index < 0) {
      return
    }
    const to = result[index]
    handleClose()
    await nextTick()
    go(to.path)
  }

  // close search modal
  function handleClose() {
    searchResult.value = []
    emit('close')
  }

  // enter search
  onKeyStroke('Enter', handleEnter)
  // Monitor keyboard arrow keys
  onKeyStroke('ArrowUp', handleUp)
  onKeyStroke('ArrowDown', handleDown)
  // esc close
  onKeyStroke('Escape', handleClose)

  return { handleSearch, searchResult, keyword, activeIndex, handleMouseenter, handleEnter }
}
